using System;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Drawing;
using Wilco.SyntaxHighlighting.Engine.Node.Implementation.CommentBlock;
using Wilco.SyntaxHighlighting.Engine.Node.Implementation.String;
using Wilco.SyntaxHighlighting.Engine.Node.Occurrence;
using Wilco.SyntaxHighlighting.Engine.Parser;
using Wilco.SyntaxHighlighting.Engine.Scanner;
using Wilco.SyntaxHighlighting.Engine.Scanner.Implementation;
using Wilco.SyntaxHighlighting.Engine.Tokenizer;

namespace Wilco.SyntaxHighlighting.Engine.Highlighter
{
    /// <summary>
    /// Represents an abstract base class for a syntax highlighter.
    /// </summary>
    public abstract class HighlighterBase : Component
    {
        private IParser parser;
        private string name;
        private string fullName;
        private readonly StringCollection tagValues;
        private readonly StringCollection fileExtensions;
        private TokenizerBase tokenizer;
        private readonly ScannerCollection scanners;
        private OccurrenceCollection scannerResult;

        /// <summary>
        /// Gets or sets the unique name for this highlighter.
        /// </summary>
        public string Name
        {
            get
            {
                return name;
            }
            set
            {
                if (value != name)
                {
                    name = value;
                }
            }
        }

        /// <summary>
        /// Gets or sets the full name for this highlighter.
        /// </summary>
        public string FullName
        {
            get
            {
                return fullName;
            }
            set
            {
                if (value != fullName)
                {
                    fullName = value;
                }
            }
        }

        /// <summary>
        /// Gets the values which can be used to specify this language.
        /// </summary>
        /// <remarks>
        /// This property can be used in different contexts such as ASP.NET context. In that case this property could 
        /// contain the possible values in order to use this highlighter.
        /// </remarks>
        public StringCollection TagValues
        {
            get
            {
                return tagValues;
            }
        }

        /// <summary>
        /// Gets the file extensions registered for the language this highlighter represents.
        /// </summary>
        public StringCollection FileExtensions
        {
            get
            {
                return fileExtensions;
            }
        }

        /// <summary>
        /// Gets or sets the parser.
        /// </summary>
        public IParser Parser
        {
            get
            {
                return parser;
            }
            set
            {
                if (value != parser)
                {
                    parser = value;
                }
            }
        }

        /// <summary>
        /// Gets the scanners which will be used to scan the source code.
        /// </summary>
        public ScannerCollection Scanners
        {
            get
            {
                return scanners;
            }
        }

        /// <summary>
        /// Gets or sets the scanner result.
        /// </summary>
        protected OccurrenceCollection ScannerResult
        {
            get
            {
                return scannerResult;
            }
            set
            {
                if (value != scannerResult)
                {
                    scannerResult = value;
                }
            }
        }

        /// <summary>
        /// Gets or sets the tokenizer.
        /// </summary>
        protected TokenizerBase Tokenizer
        {
            get
            {
                return tokenizer;
            }
            set
            {
                if (value != tokenizer)
                {
                    tokenizer = value;
                }
            }
        }

        /// <summary>
        /// Initializes a new instance of a <see cref="HighlighterBase"/> class.
        /// </summary>
        /// <param name="parser">The parser which will be used to parse the source code.</param>
        public HighlighterBase(IParser parser)
        {
            this.parser = parser;
            tagValues = new StringCollection();
            fileExtensions = new StringCollection();
            scanners = new ScannerCollection();

            IScanner entryPointScanner = BuildEntryPointScanner(new CharTokenizer(), new OccurrenceCollection());
            if (entryPointScanner != null)
                scanners.Add(entryPointScanner);
        }

        /// <summary>
        /// Creates a new instance (clone) of this highlighter.
        /// </summary>
        /// <returns></returns>
        public abstract HighlighterBase Create();

        /// <summary>
        /// Forces the highlighter to be reset.
        /// </summary>
        public void ForceReset()
        {
            scanners.Clear();

            IScanner entryPointScanner = BuildEntryPointScanner(new CharTokenizer(), new OccurrenceCollection());
            if (entryPointScanner != null)
                scanners.Add(entryPointScanner);
        }

        /// <summary>
        /// Builds a new chain of scanners.
        /// </summary>
        /// <param name="tokenizer">The tokenizer used by the scanners.</param>
        /// <param name="scannerResult">The scanner result.</param>
        /// <returns>
        /// The scanner at the start of the chain.
        /// </returns>
        public virtual IScanner BuildEntryPointScanner(TokenizerBase tokenizer, OccurrenceCollection scannerResult)
        {
            this.tokenizer = tokenizer;
            this.scannerResult = scannerResult;

            IScanner wordScanner = BuildWordScanner();

            IScanner stringScanner = BuildStringScanner();
            stringScanner.Child = wordScanner;

            IScanner commentLineScanner = BuildCommentLineScanner();
            commentLineScanner.Child = stringScanner;

            IScanner commentBlockScanner = BuildCommentBlockScanner();
            commentBlockScanner.Child = commentLineScanner;

            return commentBlockScanner;
        }

        /// <summary>
        /// Builds a word scanner.
        /// </summary>
        /// <returns>A <see cref="WordScanner"/> object.</returns>
        protected virtual IScanner BuildWordScanner()
        {
            WordScanner scanner = new WordScanner(tokenizer, scannerResult);
            return scanner;
        }

        /// <summary>
        /// Builds a string scanner.
        /// </summary>
        /// <returns>A <see cref="StringLineScanner"/> object.</returns>
        protected virtual IScanner BuildStringScanner()
        {
            StringLineScanner scanner = new StringLineScanner(tokenizer, scannerResult);
            scanner.StringNode.ForeColor = Color.DarkRed;
            scanner.StringNode.Entities.Add(new StringEntity("\"", "\"", "\\"));
            return scanner;
        }

        /// <summary>
        /// Builds a comment line scanner.
        /// </summary>
        /// <returns>A <see cref="CommentLineScanner"/> object.</returns>
        protected virtual IScanner BuildCommentLineScanner()
        {
            CommentLineScanner scanner = new CommentLineScanner(tokenizer, scannerResult);
            scanner.CommentLineNode.ForeColor = Color.Green;
            scanner.CommentLineNode.Entities.Add("//");
            return scanner;
        }

        /// <summary>
        /// Builds a comment line scanner.
        /// </summary>
        /// <returns>A <see cref="CommentBlockScanner"/> object.</returns>
        protected virtual IScanner BuildCommentBlockScanner()
        {
            CommentBlockScanner scanner = new CommentBlockScanner(tokenizer, scannerResult);
            scanner.CommentBlockNode.ForeColor = Color.Green;
            scanner.CommentBlockNode.Entities.Add(new Entity("/*", "*/"));
            return scanner;
        }

        /// <summary>
        /// Parses the source code.
        /// </summary>
        /// <param name="source">The source code to parse.</param>
        /// <returns>The parsed source code.</returns>
        public virtual string Parse(string source)
        {
            if (parser == null)
                throw new Exception("There is no parser set for this highlighter.");

            // Clean up.
            tokenizer.Reset();
            scannerResult.Clear();
            for (int i = 0; i < scanners.Count; i++)
                ResetScanner(scanners[i]);
			
            tokenizer.Tokenize(source);
            while (tokenizer.MoveNext())
            {
                for (int i = 0; i < scanners.Count; i++)
                {
                    scanners[i].Scan(tokenizer.GetNextTokens(1));
                }
            }

            // Sort the collection based on the start index of the occurrence, to simplify the job of the parser.
            scannerResult.Sort(delegate(Occurrence lhs, Occurrence rhs)
                                        {
                                            return lhs.Start - rhs.Start;
                                        });

            return parser.Parse(source, scannerResult);
        }

        /// <summary>
        /// Resets a chain of scanners.
        /// </summary>
        /// <param name="scanner">The <see cref="IScanner"/> implementation scanner which should be resetted.</param>
        private void ResetScanner(IScanner scanner)
        {
            scanner.Reset();
            if (scanner.Child != null)
                ResetScanner(scanner.Child);
        }
    }
}